package com.mhh.selary.backend.api.impl.user;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.mhh.sdk.beans.LoginUserContext;
import com.mhh.sdk.beans.dto.LoginUser;
import com.mhh.sdk.beans.dto.PageResultDTO;
import com.mhh.sdk.beans.enums.DeleteEnum;
import com.mhh.sdk.beans.exception.BusinessException;
import com.mhh.sdk.utils.date.DateUtils;
import com.mhh.sdk.utils.security.JwtUtils;
import com.mhh.sdk.utils.security.MD5Utils;
import com.mhh.sdk.utils.security.SecretUtils;
import com.mhh.selary.backend.api.user.ISelaryUserAccountService;
import com.mhh.selary.backend.config.SystemConfig;
import com.mhh.selary.backend.config.SystemConstants;
import com.mhh.selary.backend.dao.api.project.ISelaryProjectDao;
import com.mhh.selary.backend.dao.api.user.ISelaryUserAccountDao;
import com.mhh.selary.backend.dao.api.user.ISelaryUserProjectDao;
import com.mhh.selary.backend.dao.entity.project.SelaryProjectEntity;
import com.mhh.selary.backend.dao.entity.user.SelaryUserAccountEntity;
import com.mhh.selary.backend.dao.entity.user.SelaryUserProjectEntity;
import com.mhh.selary.backend.dto.IdDTO;
import com.mhh.selary.backend.dto.user.LoginDTO;
import com.mhh.selary.backend.dto.user.LoginSuccessDTO;
import com.mhh.selary.backend.dto.user.UserChangePwdDTO;
import com.mhh.selary.backend.dto.user.UserInfoDTO;
import com.mhh.selary.backend.dto.user.UserInitPwdDTO;
import com.mhh.selary.backend.dto.user.UserListDTO;
import com.mhh.selary.backend.dto.user.UserQueryDTO;
import com.mhh.selary.backend.dto.user.UserResetPwdDTO;
import com.mhh.selary.backend.dto.user.UserSaveDTO;
import com.mhh.selary.backend.dto.user.UserSwitchDTO;
import com.mhh.selary.backend.enums.BizErrorEnum;
import com.mhh.selary.backend.enums.UserRoleEnum;
import com.mhh.selary.backend.enums.UserStatusEnum;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.stream.Collectors;

/**
 * SelaryUserAccountServiceImpl
 *
 * @author 人工智能
 * @date 2020-11-11 14:24:36
 */
@Slf4j
@Service
public class SelaryUserAccountServiceImpl implements ISelaryUserAccountService {

    @Autowired
    private ISelaryUserAccountDao userAccountDao;

    @Autowired
    private ISelaryUserProjectDao userProjectDao;

    @Autowired
    private ISelaryProjectDao projectDao;

    @Autowired
    private SystemConfig systemConfig;

    @Override
    public LoginSuccessDTO login(LoginDTO dto) {
        LambdaQueryWrapper<SelaryUserAccountEntity> queryWrapper = new LambdaQueryWrapper<SelaryUserAccountEntity>()
                .eq(SelaryUserAccountEntity::getUsername, dto.getUsername());
        SelaryUserAccountEntity accountEntity = userAccountDao.getOne(queryWrapper);
        if (accountEntity == null || DeleteEnum.isDelete(accountEntity.getIsDelete())) {
            throw new BusinessException(BizErrorEnum.USER_PWD_ERROR);
        }
        if (!accountEntity.getPassword().equals(MD5Utils.getMD5(dto.getPassword()))) {
            throw new BusinessException(BizErrorEnum.USER_PWD_ERROR);
        }
        if (UserStatusEnum.DISABLE.getStatus().equals(accountEntity.getStatus())) {
            throw new BusinessException(BizErrorEnum.USER_DISABLED);
        }
        String token = JwtUtils.generatorToken(systemConfig.getApplicationName(), accountEntity.getId(), accountEntity.getUsername(), accountEntity.getPassword(), systemConfig.getExpire());
        SystemConstants.cacheLoginToken(accountEntity.getId(), token);
        boolean needChangePwd = UserStatusEnum.INIT.getStatus().equals(accountEntity.getStatus());
        return LoginSuccessDTO.builder().username(accountEntity.getUsername()).role(accountEntity.getRole()).token(token).needChangePwd(needChangePwd).build();
    }

    @Override
    public LoginUser loginVerify(String token) {
        //从token解码出用户信息
        JwtUtils.TokenAccess tokenAccess = this.decodeToken(token);
        Long userId = tokenAccess.getUserId();
        //校验账号是否存在，用户名是否匹配
        SelaryUserAccountEntity accountEntity = userAccountDao.getById(userId);
        //验证账号的状态
        this.verifyAdminValid(accountEntity);
        //验证token的签名合法性
        return this.verifyTokenSign(accountEntity, token);
    }

    @Override
    public PageResultDTO<UserListDTO> list(UserQueryDTO dto) {
        LambdaQueryWrapper<SelaryUserAccountEntity> queryWrapper = new LambdaQueryWrapper<SelaryUserAccountEntity>()
                .like(StringUtils.isNotBlank(dto.getUsername()), SelaryUserAccountEntity::getUsername, dto.getUsername())
                .eq(dto.getRole() != null, SelaryUserAccountEntity::getRole, dto.getRole())
                .eq(dto.getStatus() != null, SelaryUserAccountEntity::getStatus, dto.getStatus());
        IPage<SelaryUserAccountEntity> page = dto.ofPage();
        page = userAccountDao.page(page, queryWrapper);
        List<UserListDTO> list = page.getRecords().stream().map(entity -> {
            UserListDTO userListDTO = new UserListDTO();
            userListDTO.setId(entity.getId());
            userListDTO.setUsername(entity.getUsername());
            userListDTO.setRoleDesc(UserRoleEnum.valueBy(entity.getRole()).getDesc());
            userListDTO.setStatus(entity.getStatus());
            userListDTO.setStatusDesc(UserStatusEnum.valueBy(entity.getStatus()).getDesc());
            userListDTO.setCreateTime(entity.getCreateTime());
            return userListDTO;
        }).collect(Collectors.toList());
        return PageResultDTO.build(list, page);
    }

    @Override
    public UserInfoDTO detail(IdDTO dto) {
        SelaryUserAccountEntity entity = userAccountDao.getById(dto.getId());
        UserInfoDTO userInfo = new UserInfoDTO();
        userInfo.setId(dto.getId());
        userInfo.setUsername(entity.getUsername());
        userInfo.setRole(entity.getRole());
        userInfo.setRoleDesc(UserRoleEnum.valueBy(entity.getRole()).getDesc());
        userInfo.setStatus(entity.getStatus());
        userInfo.setStatusDesc(UserStatusEnum.valueBy(entity.getStatus()).getDesc());
        if (UserStatusEnum.INIT.getStatus().equals(entity.getStatus())) {
            userInfo.setInitPwd(entity.getInitPwd());
        } else {
            userInfo.setInitPwd("******");
        }
        userInfo.setCreateTime(entity.getCreateTime());
        LambdaQueryWrapper<SelaryUserProjectEntity> queryWrapper = new LambdaQueryWrapper<SelaryUserProjectEntity>()
                .eq(SelaryUserProjectEntity::getUserId, dto.getId());
        List<Long> projectIds = userProjectDao.list(queryWrapper).stream().map(SelaryUserProjectEntity::getProjectId).collect(Collectors.toList());
        userInfo.setProjectIds(projectIds);
        if (CollectionUtils.isNotEmpty(projectIds)) {
            List<String> projectNames = projectDao.listByIds(projectIds).stream().map(SelaryProjectEntity::getProjectName).collect(Collectors.toList());
            userInfo.setProjectNames(projectNames);
        }
        return userInfo;
    }

    @Override
    public void save(UserSaveDTO dto) {
        this.validateUsername(dto.getId(), dto.getUsername());
        SelaryUserAccountEntity entity = new SelaryUserAccountEntity();
        entity.setId(dto.getId());
        if (dto.getId() == null) {
            entity.setUsername(dto.getUsername());
            String initPwd = SecretUtils.generatorPassword(8, 12);
            entity.setInitPwd(initPwd);
            entity.setPassword(MD5Utils.getMD5(initPwd));
        }
        entity.setRole(dto.getRole());
        LoginUser loginUser = LoginUserContext.get();
        if (dto.getId() == null) {
            entity.setCreateBy(loginUser.getUserId());
            entity.setCreateByType(loginUser.getUserType());
            entity.setCreateTime(DateUtils.currentTime());
        } else {
            entity.setUpdateBy(loginUser.getUserId());
            entity.setUpdateByType(loginUser.getUserType());
            entity.setUpdateTime(DateUtils.currentTime());
        }
        userAccountDao.saveOrUpdate(entity);

        if (dto.getId() != null) {
            LambdaQueryWrapper<SelaryUserProjectEntity> queryWrapper = new LambdaQueryWrapper<SelaryUserProjectEntity>()
                    .eq(SelaryUserProjectEntity::getUserId, dto.getId());
            List<Long> projectIds = userProjectDao.list(queryWrapper).stream().map(SelaryUserProjectEntity::getProjectId).collect(Collectors.toList());
            boolean sameCollection = (CollectionUtils.isEmpty(dto.getProjectIds()) && CollectionUtils.isEmpty(projectIds))
                    || CollectionUtils.isEqualCollection(dto.getProjectIds(), projectIds);
            if (sameCollection) {
                return;
            }
            if (CollectionUtils.isNotEmpty(projectIds)) {
                userProjectDao.remove(queryWrapper);
            }
        }

        if (CollectionUtils.isNotEmpty(dto.getProjectIds())) {
            List<SelaryUserProjectEntity> userProjectEntityList = dto.getProjectIds().stream().map(projectId -> {
                SelaryUserProjectEntity userProjectEntity = new SelaryUserProjectEntity();
                userProjectEntity.setUserId(entity.getId());
                userProjectEntity.setProjectId(projectId);
                entity.setCreateBy(loginUser.getUserId());
                entity.setCreateByType(loginUser.getUserType());
                entity.setCreateTime(DateUtils.currentTime());
                return userProjectEntity;
            }).collect(Collectors.toList());
            userProjectDao.saveBatch(userProjectEntityList);
        }
    }

    @Override
    public void initPwd(UserInitPwdDTO dto) {
        LoginUser loginUser = LoginUserContext.get();
        this.updatePassword(loginUser.getUserId(), dto.getNewPassword());
    }

    @Override
    public void changePwd(UserChangePwdDTO dto) {
        LoginUser loginUser = LoginUserContext.get();
        SelaryUserAccountEntity userAccountEntity = userAccountDao.getById(loginUser.getUserId());
        if (!MD5Utils.getMD5(dto.getOldPassword()).equals(userAccountEntity.getPassword())) {
            throw new BusinessException(BizErrorEnum.OLD_PASSWORD_ERROR);
        }
        this.updatePassword(loginUser.getUserId(), dto.getNewPassword());
    }

    private void updatePassword(Long userId, String newPassword) {
        UpdateWrapper<SelaryUserAccountEntity> updateWrapper = new UpdateWrapper<>();
        updateWrapper.eq("id", userId);
        updateWrapper.set("password", MD5Utils.getMD5(newPassword));
        updateWrapper.set("init_pwd", null);
        updateWrapper.set("status", UserStatusEnum.ENABLE.getStatus());
        userAccountDao.update(new SelaryUserAccountEntity(), updateWrapper);
    }

    @Override
    public UserResetPwdDTO resetPwd(IdDTO dto) {
        SelaryUserAccountEntity userAccountEntity = userAccountDao.getById(dto.getId());
        String newPassword = SecretUtils.generatorPassword(12, 16);
        userAccountEntity.setInitPwd(newPassword);
        userAccountEntity.setPassword(MD5Utils.getMD5(newPassword));
        userAccountEntity.setStatus(UserStatusEnum.INIT.getStatus());
        userAccountDao.updateById(userAccountEntity);
        return UserResetPwdDTO.builder().newPassword(newPassword).build();
    }

    @Override
    public void switchUser(UserSwitchDTO dto) {
        SelaryUserAccountEntity userAccountEntity = userAccountDao.getById(dto.getId());
        if (dto.getEnable() && (UserStatusEnum.INIT.getStatus().equals(userAccountEntity.getStatus()) || UserStatusEnum.ENABLE.getStatus().equals(userAccountEntity.getStatus()))) {
            return;
        }
        if (!dto.getEnable() && UserStatusEnum.DISABLE.getStatus().equals(userAccountEntity.getStatus())) {
            return;
        }
        UserStatusEnum statusEnum;
        if (dto.getEnable()) {
            statusEnum = StringUtils.isBlank(userAccountEntity.getInitPwd()) ? UserStatusEnum.ENABLE : UserStatusEnum.INIT;
        } else {
            statusEnum = UserStatusEnum.DISABLE;
        }
        userAccountEntity.setStatus(statusEnum.getStatus());
        userAccountDao.updateById(userAccountEntity);
    }

    @Override
    public void deleteUser(IdDTO dto) {
        userAccountDao.removeById(dto.getId());
    }

    private void validateUsername(Long id, String username) {
        LambdaQueryWrapper<SelaryUserAccountEntity> queryWrapper = new LambdaQueryWrapper<SelaryUserAccountEntity>()
                .eq(SelaryUserAccountEntity::getUsername, username);
        SelaryUserAccountEntity userAccount = userAccountDao.getOne(queryWrapper);
        if (userAccount == null) {
            return;
        }
        if (id != null && userAccount.getId().equals(id)) {
            return;
        }
        throw new BusinessException(BizErrorEnum.USERNAME_EXIST);
    }

    /**
     * 验证token的非法性，并从中解析出userId，userName
     *
     * @param token
     * @return
     */
    private JwtUtils.TokenAccess decodeToken(String token) {
        if (StringUtils.isBlank(token)) {
            throw new BusinessException(BizErrorEnum.NO_LOGIN);
        }
        JwtUtils.TokenAccess tokenAccess;
        try {
            tokenAccess = JwtUtils.decode(token);
        } catch (SecurityException e) {
            throw new BusinessException(BizErrorEnum.NO_AUTHORIZATION);
        }
        //判断token是否过期
        String cacheToken = SystemConstants.getLoginToken(tokenAccess.getUserId());
        if (StringUtils.isBlank(cacheToken)) {
            throw new BusinessException(BizErrorEnum.NO_AUTHORIZATION);
        }
        // 用户重新登录会返回新的token， 用之前老的token去访问其他请求会报错
        if (!token.equals(cacheToken)) {
            throw new BusinessException(BizErrorEnum.ADMIN_LOGIN_BY_OTHERS);
        }
        return tokenAccess;
    }

    /**
     * 验证token的签名
     *
     * @param accountEntity
     * @param token
     * @return
     */
    private LoginUser verifyTokenSign(SelaryUserAccountEntity accountEntity, String token) {
        // 验证token的签名
        Long userId = accountEntity.getId();
        String userName = accountEntity.getUsername();
        String password = accountEntity.getPassword();
        try {
            JwtUtils.verifyToken(systemConfig.getApplicationName(), userId, userName, password, token);
        } catch (SecurityException e) {
            throw new BusinessException(BizErrorEnum.NO_AUTHORIZATION);
        }
        return LoginUser.builder().userId(userId).userName(userName).userType(accountEntity.getRole()).build();
    }

    private void verifyAdminValid(SelaryUserAccountEntity accountEntity) {
        if (accountEntity == null || DeleteEnum.YES.getIsDelete().equals(accountEntity.getIsDelete())) {
            throw new BusinessException(BizErrorEnum.ADMIN_HAS_DELETE);
        }
        if (UserStatusEnum.isForbidden(accountEntity.getStatus())) {
            throw new BusinessException(BizErrorEnum.ADMIN_STATUS_FORBIDDEN);
        }
    }
}
